package adfc2.util;

import java.io.IOException;
import java.util.LinkedList;
import java.util.List;

import static adfc2.util.Lexer.TokenType.*;

/**
 * Заготовка для парсера. Содержит много полезных методов
 */
public abstract class Parser {
    protected final Lexer lexer;
    protected Lexer.Token<?> last;
    protected Lexer.Token<?> peek;

    public Parser(Lexer lexer) {
        this.lexer = lexer;
    }

    protected final Lexer.Token<?> pop() throws IOException {
        last = peek;
        peek = lexer.next();
        return peek;
    }

    protected final void expect(Lexer.TokenType type) throws IOException {
        if (!check(type)) throw new IOException("Expected " + type + ", got " + peek);
    }

    protected final <T> void expect(Lexer.TokenType type, T value) throws IOException {
        if (!check(type, value)) throw new IOException("Expected " + new Lexer.Token<T>(type, value) + ", got " + peek);
    }

    protected final void expect(char c) throws IOException {
        expect(SYMBOL, c);
    }

    protected final void expect(String s) throws IOException {
        expect(ID, s);
    }

    protected final long getLong() throws IOException {
        expect(LONGINT);
        return (Long) last.param;
    }

    protected final int getInt() throws IOException {
        return (int) getLong();
    }

    protected final double getDouble() throws IOException {
        // expect DOUBLE or LONGINT
        if (!check(DOUBLE)) expect(LONGINT);
        return ((Number) last.param).doubleValue();
    }

    protected final String getId() throws IOException {
        expect(ID);
        return (String) last.param;
    }

    protected final String getString() throws IOException {
        // expect STRING or ID
        if (!check(STRING)) {
            expect(ID);
        }
        return (String) last.param;
    }

    protected final double[] getDoubleArray(int length) throws IOException {
        double[] result = getDoubleArray();
        if (result.length != 3) throw new IOException("Expected array of size " + length + ", got " + result.length);
        return result;
    }

    protected final double[] getDoubleArray() throws IOException {
        expect('(');
        if (check(')')) return new double[]{};
        List<Double> doubles = new LinkedList<Double>();
        doubles.add(getDouble());
        while (!check(')')) {
            expect(',');
            doubles.add(getDouble());
        }
        double[] array = new double[doubles.size()];
        int i = 0;
        for (Double aDouble : doubles) {
            array[i++] = aDouble;
        }
        return array;
    }

    protected final double[] getDoubleOrArray() throws IOException {
        double[] v;
        if (checkNumber()) {
            v = new double[]{((Number) last.param).doubleValue()};
        } else {
            v = getDoubleArray();
        }
        return v;
    }

    protected final boolean check(Lexer.TokenType type) throws IOException {
        if (peek.type == type) {
            pop();
            return true;
        } else return false;
    }

    protected final boolean checkNumber() throws IOException {
        return check(LONGINT) || check(DOUBLE);
    }

    protected final boolean checkString() throws IOException {
        return check(STRING) || check(ID);
    }

    protected final <T> boolean check(Lexer.TokenType type, T value) throws IOException {
        if (peek.type == type && (peek.param == value || peek.param.equals(value))) {
            pop();
            return true;
        } else return false;
    }

    protected final boolean check(char c) throws IOException {
        return check(SYMBOL, c);
    }

    protected final boolean check(String s) throws IOException {
        return check(STRING, s) || check(ID, s);
    }

    protected final void ready() throws IOException {
        if (peek == null) pop();
    }
}
